package domain;

import java.util.*;

import entities.concept.DomainConcept;
import entities.concept.DomainConceptEQ;
import entities.concept.DomainConceptLT;
import entities.concept.DomainConceptLTEQ;

/**
 * No multi-graph is allowed, i.e., when multiple edges occur for any two vertices, 
 * the edges will be updated sequentially and only one edge will be maintained.
 * @author j55wu
 *
 */
public class FeatureGraph {

	public final static int INTEQ = 0;
	public final static int INTLT = -1;
	public final static int INTLTEQ = 1;
	
	private HashSet<Vertex> vertices;
	private HashSet<Edge> edges;
	private HashSet<String> domainStrings;
	private boolean initialClash = false;
	private HashMap<Object, Vertex> FV_MAP = new HashMap<Object, Vertex>();
	private static final boolean Early_Cut = true;
	
	public FeatureGraph() {
		this.vertices = new HashSet<Vertex>();
		this.edges = new HashSet<Edge>();
		this.domainStrings = new HashSet<String>();
	}
	
	public HashSet<Vertex> getVertices(){
		return this.vertices;
	}
	
	public HashMap<Object, Vertex> getFVMAP(){
		return this.FV_MAP;
	}
	
	
	public void buildGraph(HashSet<DomainConcept<?,?>> dcs){
		//build initial edges
		addEdge(dcs);
		//build domain concepts, then add more edges
		for(DomainConcept<?, ?> dc:dcs){
			//if given string1 OP string2. then  test if a clash exists.
			int count = 0;
			if(!dc.isLHSRole()){
				count++;
				this.domainStrings.add(dc.getFeature().toString());
			}
			if(!dc.isRHSRole()){
				count++;
				this.domainStrings.add(dc.getValue().toString());
			}
			if(count == 2){
				//two strings
				String s1 = (String)dc.getFeature();
				String s2 = (String)dc.getValue();
				int res = s1.compareTo(s2);
				if (res == 0 && dc.getOp()== INTLT
						|| res < 0 && dc.getOp() == INTEQ
						|| res > 0 ){ 
					initialClash = true;
					if(Early_Cut){
						return;
					}
				} 
			}
		}
		if(this.domainStrings.size()>1){
			addEdge(this.buildDomainConcepts(this.domainStrings));
		}
	}
	
	//avoid duplicate vertices for the same role/string!
	public Vertex shareVertex(Object f){
		if(FV_MAP.containsKey(f)){
			return FV_MAP.get(f);
		}else{
			return new Vertex(f, this);
		}
	}
	
	//find clashes in this graph
	public boolean clashFinder(){
		if(initialClash){
			return true;
		}
		boolean found = false;
		for(Vertex v:this.vertices){
			found |= v.computeLTClash(v);
			found |= v.computeEQClash(v);
			if(found){
				break;
			}
		}
		return found;
	}
	
	public boolean addEdge(HashSet<DomainConcept<?, ?>> dcs){
		boolean ret = true;
		for(DomainConcept<?, ?> dc:dcs){
			ret &= this.addEdge(dc);			
			/*if(dc instanceof DomainConceptEQ){
				DomainConceptEQ de = new DomainConceptEQ(dc.getValue(), dc.getFeature());
				ret &= this.addEdge(de);
			}*/
		}
		return ret;
	}
	
	//add a triple (op f k) to the graph, true if added successfully
	public boolean addEdge(DomainConcept<?, ?> dc){
		Vertex src = this.shareVertex(dc.getFeature());
		Vertex dest = this.shareVertex(dc.getValue());
		int op = this.getLabel(dc);
		//duplicate edges
		Edge test = this.duplicateEdge(src, dest);
		if(test != null){
			//System.out.println("Update edge:"+test.toString());
			if(this.opClash(test.label, op)){
				initialClash = true;
				if(Early_Cut){
					return false;
				}
			}
			//update the edges: remove old edge from vertices, and add this new edge!
			this.edges.remove(test);
			src.getOutEdges().remove(test);
			dest.getInEdges().remove(test);
		}
		Edge e = new Edge(op, src, dest); 
		this.edges.add(e);
		src.addOutEdge(e);
		dest.addInEdge(e);
		//if f=g, we also add g=f automatically
		if(dc instanceof DomainConceptEQ){
			Edge test2 = this.duplicateEdge(dest, src);
			if(test2 != null){
				if(this.opClash(test2.label, op)){
					initialClash = true;
					if(Early_Cut){
						return false;
					}
				}
				this.edges.remove(test2);
				dest.getOutEdges().remove(test2);
				src.getInEdges().remove(test2);
			}
			Edge e2 = new Edge(op, dest, src); 
			this.edges.add(e2);
			dest.addOutEdge(e2);
			src.addInEdge(e2);		
		}	
		
		this.vertices.add(src);
		this.vertices.add(dest);
		return true;
	}
	
	/*
	 * clash if < -> = or = -> <, i.e., one is less than, the other is equal.
	 */
	public boolean opClash(int op1, int op2){
		return op1+op2 == -1;
	}
	
	public Edge duplicateEdge(Vertex src, Vertex dest){
		for(Edge e:this.edges){
			if(e.identicalVertices(src, dest)){
				return e;
			}
		}
		return null;
	}

	public int getLabel(DomainConcept<?, ?> dc){
		int i=0;
		if(dc instanceof DomainConceptEQ){
			i = INTEQ;
		}else if(dc instanceof DomainConceptLT){
			i = INTLT;
		}else if(dc instanceof DomainConceptLTEQ){
			i = INTLTEQ;
		}
		return i;
	}
	
	/**
	 * Find all relationships between any two concrete elements, e.g., 3<4, 4<5 1<2
	 */
	public HashSet<DomainConcept<?, ?>>  buildDomainConcepts(HashSet<String> ss){
		HashSet<DomainConcept<?, ?>> dcs = new HashSet<DomainConcept<?, ?>> ();
		while(!ss.isEmpty()){
			String s = ss.iterator().next();
			ss.remove(s);
			for(String c:ss){
				int res = s.compareTo(c);
				if(res == 0){
					dcs.add(new DomainConceptEQ<String, String>(s,c));
				}else if(res > 0){
					dcs.add(new DomainConceptLT<String, String>(c,s)); 
				}else if(res < 0){
					dcs.add(new DomainConceptLT<String, String>(s,c));
				}
			}
		}
//		if(ss.size()>1){
//			dcs.addAll(buildDomainConcepts(ss));
//		}

		return dcs;
	}
	
	 
}
